/*
    Deferred Shader
*/

uniform sampler2D albedoTex;       
uniform sampler2D normalTex;            // the alpha channel of this tex stores the Eye-space depth
uniform sampler2D factorTex;
uniform sampler2D mvPosTex;
uniform sampler2D rnm;


varying vec2 coord;
varying vec2 uv;

#define SAMPLES 10      // 10 samples gives very good results already
#define EPSILON 0.000001
#define VLN 10000000000.0
#define LIGHTS 12
#define AMB 0.05
#define OVERBRIGHT_THRESHOLD 1.2

// SSAO Settings
const float totStrength = 1.0;
const float strength = 0.5;
const float offset = 17.2;
const float falloff = 0.0000002;
const float rad = 0.5;
const float invSamples = -totStrength/SAMPLES;
const float depthAdjust = 0.35;

// lights, in eye-space
uniform vec4 lights[LIGHTS];        // position
uniform vec3 lights_color[LIGHTS]; // color
uniform float lights_dist[LIGHTS];  // distance
uniform float lights_spec[LIGHTS];

// switches
uniform int sw_AO = 1;
uniform int sw_AO_ONLY = -1;
uniform int sw_ALL_LIGHTS;
uniform int sw_EDGE = -1;

// uniform settings
uniform float LIGHT_STRENGTH = 1.0;
uniform float LIGHT_DIST = 1.0;
uniform float HEIGHT;
uniform float WIDTH;


// overbright
const vec3 overbright_reduction = vec3(OVERBRIGHT_THRESHOLD);

// useful stuff
const vec4 CL = vec4(1.0, 0.0, 0.5, 0.25);




/*
 * Screen-Space Ambient Occlusion
 */
float Do_SSAO(in vec2 uv, in vec3 norm, in float currentPixelDepth) {
    #if SAMPLES == 16
    vec3 pSphere[16] = vec3[16](
        vec3(0.53812504, 0.18565957, -0.43192),
        vec3(0.13790712, 0.24864247, 0.44301823),
        vec3(0.33715037, 0.56794053, -0.005789503),
        vec3(-0.6999805, -0.04511441, -0.0019965635),
        vec3(0.06896307, -0.15983082, -0.85477847),
        vec3(0.056099437, 0.006954967, -0.1843352),
        vec3(-0.014653638, 0.14027752, 0.0762037),
        vec3(0.010019933, -0.1924225, -0.034443386),
        vec3(-0.35775623, -0.5301969, -0.43581226),
        vec3(-0.3169221, 0.106360726, 0.015860917),
        vec3(0.010350345, -0.58698344, 0.0046293875),
        vec3(-0.08972908, -0.49408212, 0.3287904),
        vec3(0.7119986, -0.0154690035, -0.09183723),
        vec3(-0.053382345, 0.059675813, -0.5411899),
        vec3(0.035267662, -0.063188605, 0.54602677),
        vec3(-0.47761092, 0.2847911, -0.0271716));
#else 
    vec3 pSphere[10] = vec3[10](
        vec3(-0.010735935, 0.01647018, 0.0062425877),
        vec3(-0.06533369, 0.3647007, -0.13746321),
        vec3(-0.6539235, -0.016726388, -0.53000957),
        vec3(0.40958285, 0.0052428036, -0.5591124),
        vec3(-0.1465366, 0.09899267, 0.15571679),
        vec3(-0.44122112, -0.5458797, 0.04912532),
        vec3(0.03755566, -0.10961345, -0.33040273),
        vec3(0.019100213, 0.29652783, 0.066237666),
        vec3(0.8765323, 0.011236004, 0.28265962),
        vec3(0.29264435, -0.40794238, 0.15964167));
#endif  
    // grab a normal for reflecting the sample rays later on
   vec3 fres = normalize((texture2D(rnm,uv*offset).xyz*2.0) - vec3(1.0));

   float bl = 0.0;
    // adjust for the depth ( not shure if this is good..)
    float radD = rad/currentPixelDepth;

    vec3 ray, se;
    float occluderDepth, depthDifference;

    for(int i=0; i < SAMPLES; ++i)
    {
      // get a vector (randomized inside of a sphere with radius 1.0) from a texture and reflect it
      ray = radD*reflect(pSphere[i],fres) * 0.3;

      // if the ray is outside the hemisphere then change direction
      se = vec3(uv.xy, currentPixelDepth * depthAdjust) + sign(dot(ray,norm))*ray;

      // get the depth of the occluder fragment
      vec4 occluderFragment = texture2D(normalTex,se.xy);

      // get the normal of the occluder fragment
      occluderDepth = occluderFragment.a;
      // if depthDifference is negative = occluder is behind current fragment
      depthDifference = currentPixelDepth-occluderDepth;
      depthDifference *= depthAdjust;

      // calculate the difference between the normals as a weight

      // the falloff equation, starts at falloff and is kind of 1/x^2 falling
      bl += step(falloff,depthDifference)*(1.0-dot(occluderFragment.xyz,norm))*(1.0-smoothstep(falloff,strength,depthDifference));
    }
 
   // output the result
   return (bl*invSamples + 1.0);
}

/*
 * Edge detect 
 */
    ////////////////////////////  
   // Neighbor offset table  
   ////////////////////////////  
vec2 offsets[9] = vec2[9](  
   vec2( 0.0,  0.0), //Center       0  
   vec2(-1.0/WIDTH, -1.0/HEIGHT), //Top Left     1  
   vec2( 0.0, -1.0/HEIGHT), //Top          2  
   vec2( 1.0/WIDTH, -1.0/HEIGHT), //Top Right    3  
   vec2( 1.0/WIDTH,  0.0), //Right        4  
   vec2( 1.0/WIDTH,  1.0/HEIGHT), //Bottom Right 5  
   vec2( 0.0, 1.0/HEIGHT), //Bottom       6  
   vec2(-1.0/WIDTH,  1.0/HEIGHT), //Bottom Left  7  
   vec2(-1.0/WIDTH,  0.0)  //Left         8  
);
float Do_EdgeDetect(in vec2 uv)  
{  
    float Depth[9];  
    vec3 Normal[9];  
    //Retrieve normal and depth data for all neighbors.  
    for (int i=0; i<9; ++i)  {
        vec2 uv2 = uv + offsets[i]; 
        vec4 normalDepth = texture2D(normalTex, uv2);
        
        Depth[i] = normalDepth.a;
        Normal[i]= normalDepth.xyz;
    }  
    
    //Compute Deltas in Depth.  
    vec4 Deltas1;  
    vec4 Deltas2;  
    Deltas1.x = Depth[1];  
    Deltas1.y = Depth[2];  
    Deltas1.z = Depth[3];  
    Deltas1.w = Depth[4];  
    Deltas2.x = Depth[5];  
    Deltas2.y = Depth[6];  
    Deltas2.z = Depth[7];  
    Deltas2.w = Depth[8];  
    //Compute absolute gradients from center.  
    Deltas1 = abs(Deltas1 - Depth[0]);  
    Deltas2 = abs(Depth[0] - Deltas2);  
    
    //Find min and max gradient, ensuring min != 0  
    vec4 maxDeltas = max(Deltas1, Deltas2);  
    vec4 minDeltas = max(min(Deltas1, Deltas2), 0.00001);  
    // Compare change in gradients, flagging ones that change  
    // significantly.  
    // How severe the change must be to get flagged is a function of the  
    // minimum gradient. It is not resolution dependent. The constant  
    // number here would change based on how the depth values are stored  
    // and how sensitive the edge detection should be.  
    vec4 depthResults = step(minDeltas * 25.0, maxDeltas);  
    
    //Compute change in the cosine of the angle between normals.  
    Deltas1.x = dot(Normal[1], Normal[0]);  
    Deltas1.y = dot(Normal[2], Normal[0]);  
    Deltas1.z = dot(Normal[3], Normal[0]);  
    Deltas1.w = dot(Normal[4], Normal[0]);  
    Deltas2.x = dot(Normal[5], Normal[0]);  
    Deltas2.y = dot(Normal[6], Normal[0]);  
    Deltas2.z = dot(Normal[7], Normal[0]);  
    Deltas2.w = dot(Normal[8], Normal[0]);
    
    Deltas1 = abs(Deltas1 - Deltas2);  
    // Compare change in the cosine of the angles, flagging changes  
    // above some constant threshold. The cosine of the angle is not a  
    // linear function of the angle, so to have the flagging be  
    // independent of the angles involved, an arccos function would be  
    // required.  
    vec4 normalResults = step(0.4, Deltas1);  
    normalResults = max(normalResults, depthResults);  
       
    return dot(normalResults, CL.wwww);
}  

void main() {
    // read in G-buffer data
    vec3 albedo = texture2D(albedoTex, uv).rgb;
    vec4 normalDepth = texture2D(normalTex, uv);
    vec4 factors = texture2D(factorTex, uv);
    
    vec3 normal = normalDepth.xyz;
    float depth = normalDepth.a;        // eye space depth
    
    float fDiff = factors.x;
    float fSpec = factors.y;
    float fGloss = factors.z;
    float afGloss = 200.0;        // gloss unpack
    if (fGloss < 0.997) {   // this is value of 254/255
        afGloss = pow(fGloss, 2.0) * 63.0 + 1.0;  // non-linear range from 1 to 64
    }
    
    
    vec3 vfSpec = vec3(fSpec * LIGHT_STRENGTH);
    
    // current fragment position
    vec3 P = texture2D(mvPosTex, uv).xyz;
   
    
    // ambient
    vec3 accum_DL = CL.yyy;  // accummulated diffuse luminance
    vec3 accum_SL = CL.yyy;  // accummulated specular luminance
    vec3 lum = CL.yyy;         // total luminance
    vec3 accum_L = CL.yyy;     // total color ( = result)
    
    float LDist, LDistsq, Id, Is, max_dist, coning;
    vec3 L, Ln, R;
    

    max_dist = 40.0 * LIGHT_DIST;
    // compute diffuse (Lambert) shading
    L = vec3(0.0, -0.2, 0.0) - P;    // Light Vector
    LDist = length(L);
    LDistsq = pow(LDist, 2.0);
    Ln = normalize(L);
    
    Id = max(dot(normal, Ln), 0.0);           // lambert term

    coning = smoothstep(0.9, 0.95, dot(Ln, CL.yyx));
    
    // compute specular (Phong) shading
    Is = 0.0;        
    R = reflect(-L, normal);
    Is = pow(max(0.0, dot(normalize(-P), normalize(R))), afGloss) * coning;
    
    vec3 color = mix(vec3(1.0, 0.8, 0.3), CL.xxx, coning);
    // accummulate luminance
    accum_SL = Is * Id + accum_SL;
    accum_DL += color * Id * coning * (1.0 - (LDistsq / max_dist));
    
    // compute result
    fDiff *= LIGHT_STRENGTH;    // affect diffuse factor
    accum_DL = accum_DL * fDiff + vec3(AMB);
    accum_SL *= vfSpec;        

    lum = accum_DL + accum_SL;
    accum_L = accum_DL * albedo + accum_SL;
    
    // compute SSAO term
    float ao = 1.0;
    //++if (sw_AO == 1) {
        ao = Do_SSAO(uv, normal, depth);
    //++} else {
    
    //++}
    
    float edge_w = 0.0;
    //++if (sw_EDGE == 1) {
        edge_w = Do_EdgeDetect(uv);
    //++} else {
    //++}
    
    // shadowing
    vec2 shift = (vec2(0.5, 0.0) - uv) * 0.1;
    float d = texture2D(normalTex, uv + shift).a;
    if (depth/d > 1.1) ao = 0.0;
    
    
    
    gl_FragData[0] = vec4(accum_L, ao);
    gl_FragData[1].rgba = vec4(lum - overbright_reduction, edge_w);
}

